热更新 HMR
webpack-dev-server.js
- 通过 express 启动一个服务器,监听文件编译结束,然后通过 socket 将文件 hash 发送给客户端
js
const path = require("path");
const fs = require("fs");
const express = require("express");
const mime = require("mime");
const webpack = require("webpack");
let config = require("./webpack.config");
let compiler = webpack(config);
class Server {
constructor(compiler) {
let lastHash;
let sockets = [];
compiler.hooks.done.tap("webpack-dev-server", (stats) => {
lastHash = stats.hash;
sockets.forEach((socket) => {
socket.emit("hash", stats.hash);
socket.emit("ok");
});
});
let app = new express();
compiler.watch({}, (err) => {
console.log("编译成功");
});
const webpackDevMiddleware = (req, res, next) => {
if (req.url === "/favicon.ico") {
return res.sendStatus(404);
}
let filename = path.join(config.output.path, req.url.slice(1));
try {
let stats = fs.statSync(filename);
if (stats.isFile()) {
let content = fs.readFileSync(filename);
res.header("Content-Type", mime.getType(filename));
res.send(content);
} else {
next();
}
} catch (error) {
return res.sendStatus(404);
}
};
app.use(webpackDevMiddleware);
this.server = require("http").createServer(app);
let io = require("socket.io")(this.server);
io.on("connection", (socket) => {
sockets.push(socket);
if (lastHash) {
socket.emit("hash", lastHash);
socket.emit("ok");
}
});
}
listen(port) {
this.server.listen(port, () => {
console.log(port + "服务启动成功!");
});
}
}
let server = new Server(compiler);
server.listen(8080);
webpack-hot-dev-client.js
- 客户端启动一个 socket 监听服务器返回的 hash, 通过 jsonp 请求新文件,实现热更新
js
let socket = io("/");
let currentHash;
let hotCurrentHash;
const onConnected = () => {
console.log("客户端已经连接");
socket.on("hash", (hash) => {
currentHash = hash;
});
socket.on("ok", () => {
hotCheck();
});
socket.on("disconnect", () => {
hotCurrentHash = currentHash = null;
});
};
function hotCheck() {
if (!hotCurrentHash || hotCurrentHash === currentHash) {
return (hotCurrentHash = currentHash);
}
hotDownloadManifest().then((update) => {
let chunkIds = Object.keys(update.c);
chunkIds.forEach((chunkId) => {
hotDownloadUpdateChunk(chunkId);
});
});
}
function hotDownloadUpdateChunk(chunkId) {
var script = document.createElement("script");
script.charset = "utf-8";
script.src = "/" + chunkId + "." + hotCurrentHash + ".hot-update.js";
document.head.appendChild(script);
}
function hotDownloadManifest() {
var url = "/" + hotCurrentHash + ".hot-update.json";
return fetch(url)
.then((res) => res.json())
.catch((error) => {
console.log(error);
});
}
window.webpackHotUpdate = (chunkId, moreModules) => {
for (let moduleId in moreModules) {
let oldModule = __webpack_require__.c[moduleId];
let { parents, children } = oldModule;
var module = (__webpack_require__.c[moduleId] = {
i: moduleId,
exports: {},
parents,
children,
hot: window.hotCreateModule(),
});
moreModules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
parents.forEach((parent) => {
let parentModule = __webpack_require__.c[parent];
parentModule.hot &&
parentModule.hot._acceptedDependencies[moduleId] &&
parentModule.hot._acceptedDependencies[moduleId]();
});
hotCurrentHash = currentHash;
}
};
socket.on("connect", onConnected);
window.hotCreateModule = () => {
var hot = {
_acceptedDependencies: {},
_acceptedDependencies: function (dep, callback) {
for (var i = 0; i < dep.length; i++) {
hot._acceptedDependencies[dep[i]] = callback;
}
},
};
return hot;
};
如何监听文件发生变化
轮询判断文件的最后编辑时间是否变化,某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout (监听到变化发生后会等到 300ms 再去执行,默认 300ms)